moveEZ

The goal of moveEZ is to create animated biplots.

Installation

You can install the development version of moveEZ from GitHub with:

# install.packages("pak")
pak::pak("MuViSU/moveEZ")

Consider a dataset \({\bf{X}}\) comprising \(n\) observations and \(p\) continuous variables, along with an additional variable representing “time.” This time variable need not correspond to chronological time; it could just as well represent another form of ordered index, such as algorithmic iterations or experimental stages.

A natural approach is to construct separate biplots for each level of the time variable, enabling the user to explore how samples and variable relationships evolve across time. However, when the time variable includes many levels, this quickly results in an overwhelming number of biplots.

This package addresses that challenge by animating a single biplot across the levels of the time variable, allowing for dynamic visualisation of temporal or sequential changes in the data.

The animation of the biplots—currently limited to PCA biplots—is based on two conceptual frameworks:

  1. Fixed Variable Frame moveplot(): A biplot is first constructed using the full dataset \({\bf{X}}\), and the animation is achieved by slicing the observations according to the “time” variable. In this approach, the variable axes remain fixed, and only the sample points are animated over time.

  2. Dynamic Frame moveplot2(): Separate biplots are constructed for each time slice of the data. Both the sample points and variable axes evolve over time, resulting in a fully dynamic animation that reflects temporal changes in the underlying data structure.

To illustrate the animated biplots, we use a climate dataset included in the package. This dataset, Africa_climate, contains climate measurements from 10 African regions over time:

library(moveEZ)
data("Africa_climate")
tibble::tibble(Africa_climate)
#> # A tibble: 960 × 9
#>    Year  Month     Region AccPrec DailyEva  Temp SoilMois  SPI6  wind
#>    <fct> <fct>     <fct>    <dbl>    <dbl> <dbl>    <dbl> <dbl> <dbl>
#>  1 1950  January   ARP      0.177  0.0316   14.8    2.75  1.62   4.07
#>  2 1950  February  ARP      0.208 -0.0249   15.4    2.22  1.32   4.24
#>  3 1950  March     ARP      0.306  0.0122   20.9    2.08  0.987  4.04
#>  4 1950  April     ARP      0.196  0.00396  24.8    1.73  0.916  3.72
#>  5 1950  May       ARP      0.590 -0.0448   28.4    2.47  0.691  3.91
#>  6 1950  June      ARP      0.32  -0.00754  30.4    1.17  0.249  4.40
#>  7 1950  July      ARP      1.33   0.00184  30.8    2.00  0.673  4.93
#>  8 1950  August    ARP      1.82  -0.00944  30.5    2.67  0.937  4.45
#>  9 1950  September ARP      0.706 -0.0107   29.7    1.98  1.22   3.67
#> 10 1950  October   ARP      0.102 -0.0259   25.9    0.976 1.65   3.18
#> # ℹ 950 more rows

We begin by constructing a standard PCA biplot using the biplotEZ package. This biplot aggregates all samples across time and colours them according to their associated region:

library(biplotEZ)
bp <- biplot(Africa_climate, scaled = TRUE) |> 
  PCA(group.aes = Africa_climate$Region) |> 
  samples(opacity = 0.8, col = scales::hue_pal()(10)) |>
  plot()

1. Fixed Variable Frame with moveplot()

Using the previously created PCA biplot object bp, the moveplot() function enables animation of the sample points over time. This function is piped with several key arguments:

move: A critical argument that controls whether the biplot is animated. If set to TRUE, the sample points are animated across time. If set to FALSE, the function returns a faceted plot showing a static biplot for each time level.

This design provides flexibility in exploring temporal dynamics in multivariate data, with options for both animated and comparative static visualisations.

Facet: move = FALSE

bp |> moveplot(time.var = "Year", group.var = "Region", hulls = TRUE, move = FALSE)

Animation: move = TRUE

# Animated Z
bp |> moveplot(time.var = "Year", group.var = "Region", hulls = TRUE, move = TRUE)

2. Dynamic Frame moveplot2()

The moveplot2() function extends the animation to both the sample points and the variable axes. Unlike moveplot(), which keeps the variable axes fixed, moveplot2() constructs a separate biplot for each time slice, allowing both components to evolve over time. The function shares the same arguments as moveplot(), with the move argument determining whether the animation is shown or presented as static facets for samples and variables.

Facet: move = FALSE

bp |> moveplot2(time.var = "Year", group.var = "Region", hulls = TRUE, move = FALSE)

When move is FALSE, a faceted plot is returned, showing the biplot at each time point. Here, both the sample coordinates and variable axes differ across facets, reflecting temporal changes in the data structure.

Animated: move = TRUE

bp |> moveplot2(time.var = "Year", group.var = "Region", hulls = TRUE, move = TRUE)

Setting move to TRUE produces an animated biplot in which both the samples and variables transition across time, offering a dynamic view of structural shifts in the multivariate space.

Notice that in both the faceted and animated biplots, there is a noticeable discontinuity in the transition from the year 1950 to 1960. From 1960 onwards, however, the biplots appear well-aligned. To address such inconsistencies, the `moveplot2() function provides two additional arguments — align.time and reflect — which enable alignment and optional axis reflections of the biplots at specified time points, resulting in smoother and more coherent animations.

Animated aligned

bp |> moveplot2(time.var = "Year", group.var = "Region", hulls = TRUE, move = TRUE,
                align.time = "1950", reflect = "x")

In the example above, we align the biplot at the 1950 time point and apply a reflection about the x-axis. Available options include:

And of course, both align.time and reflect can be vectors when alignment is needed at multiple time points. Each entry in reflect corresponds to a time point in align.time, allowing fine-grained control over the alignment and orientation of biplots across the animation sequence.

Still to Come!

Watch this space! We are actively working on enhancing moveplot2() by automatically aligning the biplots across time slices. This will allow for smoother transitions and more interpretable animations when both samples and variable axes evolve.

Stay tuned for updates!